package com.gitee.kamismile.stoneComEx.common.component.support.server;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.gitee.kamismile.stoneComEx.common.component.server.FluxLocaleResolver;
import com.gitee.kamismile.stoneComEx.common.component.support.annotation.XssParameter;
import com.gitee.kamismile.stone.commmon.util.ValueUtils;
import com.gitee.kamismile.stoneComEx.common.filter.server.LocaleChangeReactiveFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
import org.springframework.web.reactive.config.ViewResolverRegistry;
import org.springframework.web.reactive.result.view.HttpMessageWriterView;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.springframework.web.util.HtmlUtils;

import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Configuration(proxyBeanMethods = true)
public class SimpleFluxWebConfig extends DelegatingWebFluxConfiguration {



//    @Autowired
//    private Jackson2JsonEncoder jackson2JsonEncoder;

//    @Bean
//    public RequestMappingHandlerMapping requestMappingHandlerMapping(RequestedContentTypeResolver requestedContentTypeResolver) {
//        RequestMappingHandlerMapping mapping = new SimpleFluxRequestMappingHandlerMapping();
//        mapping.setOrder(0);
//        mapping.setContentTypeResolver(requestedContentTypeResolver);
//        mapping.setCorsConfigurations(getCorsConfigurations());
//
////        PathMatchConfigurer configurer = getPathMatchConfigurer();
////        Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
////        Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
////        if (useTrailingSlashMatch != null) {
////            mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
////        }
////        if (useCaseSensitiveMatch != null) {
////            mapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
////        }
//        return mapping;
//    }

    @Override
    protected LocaleContextResolver createLocaleContextResolver() {
        return new FluxLocaleResolver();
    }
//    @Bean
//    @Order(0)
//    public WebExceptionHandler responseStatusExceptionHandler() {
//        return new OtherFluxExceptionHandler();
//    }

    @Bean
    public LocaleChangeReactiveFilter localeChangeReactiveFilter() {
        return new LocaleChangeReactiveFilter(localeContextResolver());
    }

    @Override
    protected void configureViewResolvers(ViewResolverRegistry registry) {
//        super.configureViewResolvers(registry);
        registry.defaultViews(new HttpMessageWriterView(jackson2JsonEncoder()));
    }


    @Override
    protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().jackson2JsonEncoder(jackson2JsonEncoder());
    }

    @Bean
    @Primary
    @ConditionalOnClass(ReloadableResourceBundleMessageSource.class)
    @ConditionalOnMissingBean(ReloadableResourceBundleMessageSource.class)
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource rbms = new ReloadableResourceBundleMessageSource();
        rbms.setDefaultEncoding("UTF-8");
        rbms.setBasename("classpath:messages/messages");
        rbms.setUseCodeAsDefaultMessage(true);
        return rbms;
    }

    @Override
    protected Validator getValidator() {
        OptionalValidatorFactoryBean optionalValidatorFactoryBean=new OptionalValidatorFactoryBean();
        optionalValidatorFactoryBean.setValidationMessageSource(messageSource());
        return optionalValidatorFactoryBean;
    }

    @Autowired
    private Jackson2ObjectMapperBuilder builder;

    @Bean
    protected Jackson2JsonEncoder jackson2JsonEncoder() {
        return new Jackson2JsonEncoder(objectMapper());
    }

    @Bean
    @Primary
    @ConditionalOnClass(ObjectMapper.class)
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = builder.build();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//        objectMapper.registerModule(new JavaTimeModule());
        objectMapper
                .setSerializerFactory(objectMapper
                        .getSerializerFactory()
                        .withSerializerModifier(new FastJsonSerializerFeatureCompatibleForJackson()));
        return objectMapper;
    }

    private class FastJsonSerializerFeatureCompatibleForJackson extends BeanSerializerModifier {
        private JsonSerializer<Object> nullStringJsonSerializer = new FastJsonSerializerFeatureCompatibleForJackson.NullStringJsonSerializer();
        private JsonSerializer<Object> nullNumberJsonSerializer = new FastJsonSerializerFeatureCompatibleForJackson.NullNumberJsonSerializer();
        private JsonSerializer<Object> nullObjectJsonSerializer = new FastJsonSerializerFeatureCompatibleForJackson.NullObjectJsonSerializer();
        private JsonSerializer<Object> nullListJsonSerializer = new FastJsonSerializerFeatureCompatibleForJackson.NullListJsonSerializer();
        private JsonSerializer<Object> xssStringJsonSerializer = new FastJsonSerializerFeatureCompatibleForJackson.XssStringJsonSerializer();


        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
                                                         List<BeanPropertyWriter> beanProperties) {
            // 循环所有的beanPropertyWriter
            beanProperties.stream().forEach(writer -> {
                JavaType propertyType = writer.getMember().getType();
                XssParameter xssParameter = writer.getAnnotation(XssParameter.class);
                Class<?> clazz = propertyType.getRawClass();
                if (clazz.equals(String.class)) {

                    if (ValueUtils.isNotNull(xssParameter) && xssParameter.xss()) {
                        writer.assignSerializer(this.xssStringJsonSerializer);
                    }

                    writer.assignNullSerializer(this.nullStringJsonSerializer);

                } else if (clazz.equals(Date.class)) {
                    writer.assignNullSerializer(this.nullStringJsonSerializer);
                } else if (clazz.equals(Integer.class) || clazz.equals(Long.class) ||
                        clazz.equals(Double.class) || clazz.equals(Float.class) ||
                        (Number.class.isAssignableFrom(clazz))) {
                    writer.assignNullSerializer(this.nullNumberJsonSerializer);
                } else if (clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class) || propertyType.isCollectionLikeType()) {
                    writer.assignNullSerializer(this.nullListJsonSerializer);
                } else {
                    //给writer注册一个自己的nullSerializer
                    writer.assignNullSerializer(this.nullObjectJsonSerializer);
                }
            });


            return beanProperties;
        }


        private class NullStringJsonSerializer extends JsonSerializer<Object> {
            @Override
            public void serialize(Object arg0, JsonGenerator arg1,
                                  SerializerProvider arg2) throws IOException, JsonProcessingException {
                if (ValueUtils.isNull(arg0)) {
                    arg1.writeString("");
                } else {
                    arg1.writeObject(arg0);
                }
            }
        }


        private class NullListJsonSerializer extends JsonSerializer<Object> {
            @Override
            public void serialize(Object arg0, JsonGenerator arg1,
                                  SerializerProvider arg2) throws IOException, JsonProcessingException {
                if (ValueUtils.isNull(arg0)) {
                    arg1.writeStartArray();
                    arg1.writeEndArray();
//                    arg1.writeObject(null);
                } else {
                    arg1.writeObject(arg0);
                }
            }
        }


        private class NullObjectJsonSerializer extends JsonSerializer<Object> {
            @Override
            public void serialize(Object arg0, JsonGenerator arg1,
                                  SerializerProvider arg2) throws IOException, JsonProcessingException {
                if (ValueUtils.isNull(arg0)) {
//                    arg1.writeStartObject();
//                    arg1.writeEndObject();
                    arg1.writeObject(null);
                } else {
                    arg1.writeObject(arg0);
                }
            }
        }

        private class XssStringJsonSerializer extends JsonSerializer<Object> {

            @Override
            public void serialize(Object value, JsonGenerator jsonGenerator,
                                  SerializerProvider serializerProvider) throws IOException {
                if (ValueUtils.isNotNull(value)) {
                    String encodedValue = HtmlUtils.htmlEscape(ValueUtils.isStringNull(value.toString()));
                    jsonGenerator.writeString(encodedValue);
                } else {
                    jsonGenerator.writeString("");
                }
            }

        }

        private class NullNumberJsonSerializer extends JsonSerializer<Object> {
            @Override
            public void serialize(Object arg0, JsonGenerator arg1,
                                  SerializerProvider arg2) throws IOException, JsonProcessingException {
                if (ValueUtils.isNull(arg0)) {
                    arg1.writeObject(null);
                } else {
                    arg1.writeObject(arg0);
                }
            }
        }
    }

//    @Override
//    protected void addCorsMappings(CorsRegistry registry) {
//        registry.addMapping("/**")
//                .allowedOrigins("*")
//                .allowedMethods("*")
//                .allowedHeaders("*");
//    }
}
